home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Internet Tools 1993 July / Internet Tools.iso / RockRidge / mail / pine / pine3.07 / c-client / imap2.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-05-11  |  54.9 KB  |  1,817 lines

  1. /*
  2.  * Program:    Interactive Mail Access Protocol 2 (IMAP2) routines
  3.  *
  4.  * Author:    Mark Crispin
  5.  *        Networks and Distributed Computing
  6.  *        Computing & Communications
  7.  *        University of Washington
  8.  *        Administration Building, AG-44
  9.  *        Seattle, WA  98195
  10.  *        Internet: MRC@CAC.Washington.EDU
  11.  *
  12.  * Date:    15 June 1988
  13.  * Last Edited:    11 May 1992
  14.  *
  15.  * Sponsorship:    The original version of this work was developed in the
  16.  *        Symbolic Systems Resources Group of the Knowledge Systems
  17.  *        Laboratory at Stanford University in 1987-88, and was funded
  18.  *        by the Biomedical Research Technology Program of the National
  19.  *        Institutes of Health under grant number RR-00785.
  20.  *
  21.  * Original version Copyright 1988 by The Leland Stanford Junior University.
  22.  * Copyright 1992 by the University of Washington.
  23.  *
  24.  *  Permission to use, copy, modify, and distribute this software and its
  25.  * documentation for any purpose and without fee is hereby granted, provided
  26.  * that the above copyright notices appear in all copies and that both the
  27.  * above copyright notices and this permission notice appear in supporting
  28.  * documentation, and that the name of the University of Washington or The
  29.  * Leland Stanford Junior University not be used in advertising or publicity
  30.  * pertaining to distribution of the software without specific, written prior
  31.  * permission.  This software is made available "as is", and
  32.  * THE UNIVERSITY OF WASHINGTON AND THE LELAND STANFORD JUNIOR UNIVERSITY
  33.  * DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, WITH REGARD TO THIS SOFTWARE,
  34.  * INCLUDING WITHOUT LIMITATION ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
  35.  * FITNESS FOR A PARTICULAR PURPOSE, AND IN NO EVENT SHALL THE UNIVERSITY OF
  36.  * WASHINGTON OR THE LELAND STANFORD JUNIOR UNIVERSITY BE LIABLE FOR ANY
  37.  * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
  38.  * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
  39.  * CONTRACT, TORT (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF
  40.  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  41.  *
  42.  */
  43.  
  44. #include <ctype.h>
  45. #include <stdio.h>
  46. #if unix
  47. #include <sys/types.h>
  48. #include <pwd.h>
  49. #include <sys/file.h>
  50. #include <sys/stat.h>
  51. #endif
  52. #include "osdep.h"
  53. #include "mail.h"
  54. #include "imap2.h"
  55. #include "misc.h"
  56.  
  57.  
  58. /* Driver dispatch used by MAIL */
  59.  
  60. DRIVER imapdriver = {
  61.   (DRIVER *) NIL,        /* next driver */
  62.   map_valid,            /* mailbox is valid for us */
  63.   map_find,            /* find mailboxes */
  64.   map_find_bboards,        /* find bboards */
  65.   map_open,            /* open mailbox */
  66.   map_close,            /* close mailbox */
  67.   map_fetchfast,        /* fetch message "fast" attributes */
  68.   map_fetchflags,        /* fetch message flags */
  69.   map_fetchenvelope,        /* fetch message envelopes */
  70.   map_fetchheader,        /* fetch message header only */
  71.   map_fetchtext,        /* fetch message body only */
  72.   map_fetchbody,        /* fetch message body section */
  73.   map_setflag,            /* set message flag */
  74.   map_clearflag,        /* clear message flag */
  75.   map_search,            /* search for message based on criteria */
  76.   map_ping,            /* ping mailbox to see if still alive */
  77.   map_check,            /* check for new messages */
  78.   map_expunge,            /* expunge deleted messages */
  79.   map_copy,            /* copy messages to another mailbox */
  80.   map_move,            /* move messages to another mailbox */
  81.   map_gc            /* garbage collect stream */
  82. };
  83.  
  84.  
  85. /* Mail Access Protocol validate mailbox
  86.  * Accepts: mailbox name
  87.  * Returns: our driver if name is valid, otherwise calls valid in next driver
  88.  */
  89.  
  90. DRIVER *map_valid (name)
  91.     char *name;
  92. {                /* assume IMAP if starts with host delimiter */
  93.   if ((*name == '{') || (*name == '*' && name[1] == '{')) return &imapdriver;
  94.                 /* else let next driver have a try */
  95.   else return imapdriver.next ? (*imapdriver.next->valid) (name) : NIL;
  96. }
  97.  
  98. /* Mail Access Protocol find list of mailboxes
  99.  * Accepts: mail stream
  100.  *        pattern to search
  101.  */
  102.  
  103. void map_find (stream,pat)
  104.     MAILSTREAM *stream;
  105.     char *pat;
  106. {
  107.   if (stream) {            /* have a mailbox stream open? */
  108.     if (LOCAL && LOCAL->use_find &&
  109.     !strcmp (imap_send (stream,"FIND MAILBOXES",pat,NIL)->key,"BAD"))
  110.       LOCAL->use_find = NIL;    /* note no finding with this server */
  111.   }
  112. #if unix            /* barf */
  113.   else {            /* make file name */
  114.     long fd;
  115.     char tmp[MAILTMPLEN];
  116.     char *s,*t;
  117.     struct stat sbuf;
  118.     sprintf (tmp,"%s/.mailboxlist",getpwuid (geteuid ())->pw_dir);
  119.     if ((fd = open (tmp,O_RDONLY,NIL)) >= 0) {
  120.       fstat (fd,&sbuf);        /* get file size and read data */
  121.       read (fd,s = (char *) fs_get (sbuf.st_size + 1),sbuf.st_size);
  122.       close (fd);        /* close file */
  123.       s[sbuf.st_size] = '\0';    /* tie off string */
  124.       if (t = strtok (s,"\n"))    /* get first mailbox name */
  125.     do if (*t == '{' && pmatch (t,pat)) mm_mailbox (t);
  126.       while (t = strtok (NIL,"\n"));
  127.     }
  128.   }
  129. #endif
  130. }
  131.  
  132.  
  133. /* Mail Access Protocol find list of bboards
  134.  * Accepts: mail stream
  135.  *        pattern to search
  136.  */
  137.  
  138. void map_find_bboards (stream,pat)
  139.     MAILSTREAM *stream;
  140.     char *pat;
  141. {
  142.                 /* this is optional, so no complaint if fail */
  143.   if (stream && LOCAL && LOCAL->use_find && LOCAL->use_bboard &&
  144.       !strcmp (imap_send (stream,"FIND BBOARDS",pat,NIL)->key,"BAD"))
  145.     LOCAL->use_bboard = NIL;
  146. }
  147.  
  148. /* Mail Access Protocol open
  149.  * Accepts: stream to open
  150.  * Returns: stream to use on success, NIL on failure
  151.  */
  152.  
  153. MAILSTREAM *map_open (stream)
  154.     MAILSTREAM *stream;
  155. {
  156.   long i,j;
  157.   char username[MAILTMPLEN];
  158.   char pwd[MAILTMPLEN];
  159.   char tmp[MAILTMPLEN];
  160.   char *host;
  161.   char *mailbox;
  162.   IMAPPARSEDREPLY *reply;
  163.   long bboard = *stream->mailbox == '*';
  164.   char *base = bboard ? stream->mailbox + 1 : stream->mailbox;
  165.   if (!(host = imap_hostfield (base))) mm_log ("Host not specified",ERROR);
  166.   else {
  167.     if (!(mailbox = imap_mailboxfield (base))) {
  168.       fs_give ((void **) &host);/* prevent storage leaks */
  169.       mm_log ("Mailbox not specified",ERROR);
  170.     }
  171.     else {
  172.       if (LOCAL) {        /* if stream opened earlier by us */
  173.     if (strcmp (ucase (strcpy (tmp,host)),
  174.             ucase (strcpy (pwd,imap_host (stream))))) {
  175.                 /* if hosts are different punt it */
  176.       sprintf (tmp,"Closing connection to %s",imap_host (stream));
  177.       if (!stream->silent) mm_log (tmp,(long) NIL);
  178.       map_close (stream);
  179.     }
  180.     else {            /* else recycle if still alive */
  181.       i = stream->silent;    /* temporarily mark silent */
  182.       stream->silent = T;    /* don't give mm_exists() events */
  183.       j = map_ping (stream);/* learn if stream still alive */
  184.       stream->silent = i;    /* restore prior state */
  185.       if (j) {        /* was stream still alive? */
  186.         sprintf (tmp,"Reusing connection to %s",host);
  187.         if (!stream->silent) mm_log (tmp,(long) NIL);
  188.         for (i = 0; i < LOCAL->cachesize; ++i) {
  189.           if (LOCAL->header[i]) fs_give ((void **) &LOCAL->header[i]);
  190.           if (LOCAL->text[i]) fs_give ((void **) &LOCAL->text[i]);
  191.         }
  192.         fs_give ((void **) &LOCAL->header);
  193.         fs_give ((void **) &LOCAL->text);
  194.         LOCAL->cachesize = 0;/* cache has been zapped */
  195.       }
  196.       else map_close (stream);
  197.     }
  198.       }
  199.  
  200.       if (!LOCAL) {        /* open new connection if no recycle */
  201.     stream->local = fs_get (sizeof (IMAPLOCAL));
  202.     LOCAL->reply.line = LOCAL->reply.tag = LOCAL->reply.key =
  203.       LOCAL->reply.text = NIL;
  204.                 /* assume maximal server */
  205.     LOCAL->use_body = LOCAL->use_find = LOCAL->use_bboard = T;
  206.     LOCAL->cachesize = 0;    /* initially no caches */
  207.     LOCAL->header = LOCAL->text = NIL;
  208.                 /* try authenticated open */
  209.     if (LOCAL->tcpstream = tcp_aopen (host,"/etc/rimapd")) {
  210.                 /* if success, see if reasonable banner */
  211.       if ((reply = imap_parse_reply (stream,tcp_getline(LOCAL->tcpstream)))
  212.           && !strcmp (reply->tag,"*") && !strcmp (reply->key,"PREAUTH"))
  213.         mm_notify (stream,reply->text,(long) NIL);
  214.                 /* nuke the stream then */
  215.       else if (LOCAL->tcpstream) {
  216.         tcp_close (LOCAL->tcpstream);
  217.         LOCAL->tcpstream = NIL;
  218.       }
  219.     }
  220.     if (!LOCAL->tcpstream &&
  221.         (LOCAL->tcpstream = tcp_open (host,IMAPTCPPORT))) {
  222.                 /* get greeting */
  223.       if (!imap_OK (stream,reply = imap_reply (stream,NIL))) {
  224.         mm_log (reply->text,ERROR);
  225.         map_close (stream);    /* clean up */
  226.       }
  227.       else {        /* only so many tries to login */
  228.         for (i = 0; i < MAXLOGINTRIALS; ++i) {
  229.           *pwd = 0;        /* initialize the password */
  230.                 /* get username and password from user */
  231.           mm_login (tcp_host (LOCAL->tcpstream),username,pwd,i);
  232.                 /* abort if he refuses to give a password */
  233.           if (*pwd == '\0') i = MAXLOGINTRIALS;
  234.           else {        /* send "LOGIN username pwd" */
  235.         j =strlen (pwd);/* portability madness */
  236.         sprintf (tmp,"{%ld}\015\012%s",j,pwd);
  237.         if (imap_OK (stream,reply = imap_send (stream,"LOGIN",username,
  238.                                tmp))) break;
  239.                 /* output failure and try again */
  240.         sprintf (tmp,"Login failed: %.80s",reply->text);
  241.         mm_log (tmp,WARN);
  242.                 /* give up now if connection died */
  243.         if (!strcmp (reply->key,"BYE")) i = MAXLOGINTRIALS;
  244.           }
  245.         }
  246.                 /* give up if too many failures */
  247.         if (i >=  MAXLOGINTRIALS) {
  248.           mm_log (*pwd ? "Too many login failures":"Login aborted",ERROR);
  249.           map_close (stream);
  250.         }
  251.       }
  252.     }
  253.                 /* failed utterly to open */
  254.     if (LOCAL && !LOCAL->tcpstream) map_close (stream);
  255.       }
  256.  
  257.       if (LOCAL) {        /* have a connection now??? */
  258.     stream->sequence++;    /* bump sequence number */
  259.                 /* prepare to update mailbox name */
  260.     fs_give ((void **) &stream->mailbox);
  261.                 /* send "SELECT mailbox" */
  262.     j = strlen (mailbox);    /* portability madness */
  263.     sprintf (tmp,"{%ld}\015\012%s",j,mailbox);
  264.     if (!imap_OK (stream,reply = imap_send (stream,bboard ? "BBOARD" :
  265.                         "SELECT",tmp,NIL))) {
  266.       sprintf (tmp,"{%s}<no mailbox>",imap_host (stream));
  267.       stream->mailbox = cpystr (tmp);
  268.       sprintf (tmp,"Can't select mailbox: %.80s",reply->text);
  269.       mm_log (tmp,ERROR);
  270.                 /* make sure dummy message counts */
  271.       mail_exists (stream,(long) 0);
  272.       mail_recent (stream,(long) 0);
  273.     }
  274.     else {            /* update mailbox name */
  275.       sprintf (tmp,"%s{%s}%s",bboard ? "*" : "",imap_host(stream),mailbox);
  276.       stream->mailbox = cpystr (tmp);
  277.                 /* note if server said it was readonly */
  278.       reply->text[11] = '\0';
  279.       stream->readonly = !strcmp (ucase (reply->text),"[READ-ONLY]");
  280.     }
  281.     if (!(stream->nmsgs || stream->silent))
  282.       mm_log ("Mailbox is empty",(long) NIL);
  283.       }
  284.       else {            /* failed to open, prevent storage leaks */
  285.     fs_give ((void **) &host);
  286.     fs_give ((void **) &mailbox);
  287.       }
  288.     }
  289.   }
  290.                 /* give up if nuked during startup */
  291.   if (LOCAL && !LOCAL->tcpstream) map_close (stream);
  292.   return LOCAL ? stream : NIL;    /* if stream is alive, return to caller */
  293. }
  294.  
  295. /* Mail Access Protocol close
  296.  * Accepts: MAIL stream
  297.  */
  298.  
  299. void map_close (stream)
  300.     MAILSTREAM *stream;
  301. {
  302.   long i;
  303.   IMAPPARSEDREPLY *reply;
  304.   if (stream && LOCAL) {    /* send "LOGOUT" */
  305.     if (LOCAL->tcpstream &&
  306.     !imap_OK (stream,reply = imap_send (stream,"LOGOUT",NIL,NIL))) {
  307.       sprintf (LOCAL->tmp,"Error in logout: %.80s",reply->text);
  308.       mm_log (LOCAL->tmp,WARN);
  309.     }
  310.                 /* close TCP connection if still open */
  311.     if (LOCAL->tcpstream) tcp_close (LOCAL->tcpstream);
  312.                 /* free up memory */
  313.     if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
  314.                 /* nuke the cache */
  315.     for (i = 0; i < LOCAL->cachesize; ++i) {
  316.       if (LOCAL->header[i]) fs_give ((void **) &LOCAL->header[i]);
  317.       if (LOCAL->text[i]) fs_give ((void **) &LOCAL->text[i]);
  318.     }
  319.     fs_give ((void **) &LOCAL->header);
  320.     fs_give ((void **) &LOCAL->text);
  321.                 /* nuke the local data */
  322.     fs_give ((void **) &stream->local);
  323.   }
  324. }
  325.  
  326.  
  327. /* Mail Access Protocol fetch fast information
  328.  * Accepts: MAIL stream
  329.  *        sequence
  330.  *
  331.  * Generally, map_fetchenvelope is preferred
  332.  */
  333.  
  334. void map_fetchfast (stream,sequence)
  335.     MAILSTREAM *stream;
  336.     char *sequence;
  337. {                /* send "FETCH sequence FAST" */
  338.   IMAPPARSEDREPLY *reply;
  339.   if (!imap_OK (stream,reply = imap_send (stream,"FETCH",sequence,"FAST"))) {
  340.     sprintf (LOCAL->tmp,"Fetch fast rejected: %.80s",reply->text);
  341.     mm_log (LOCAL->tmp,ERROR);
  342.   }
  343. }
  344.  
  345. /* Mail Access Protocol fetch flags
  346.  * Accepts: MAIL stream
  347.  *        sequence
  348.  */
  349.  
  350. void map_fetchflags (stream,sequence)
  351.     MAILSTREAM *stream;
  352.     char *sequence;
  353. {                /* send "FETCH sequence FLAGS" */
  354.   IMAPPARSEDREPLY *reply;
  355.   if (!imap_OK (stream,reply = imap_send (stream,"FETCH",sequence,"FLAGS"))){
  356.     sprintf (LOCAL->tmp,"Fetch flags rejected: %.80s",reply->text);
  357.     mm_log (LOCAL->tmp,ERROR);
  358.   }
  359. }
  360.  
  361.  
  362. /* Mail Access Protocol fetch envelope
  363.  * Accepts: MAIL stream
  364.  *        message # to fetch
  365.  * Returns: envelope of this message
  366.  *
  367.  * Fetches the "fast" information as well
  368.  */
  369.  
  370. ENVELOPE *map_fetchenvelope (stream,msgno)
  371.     MAILSTREAM *stream;
  372.     long msgno;
  373. {
  374.   long i = msgno;
  375.   long j = min (msgno+MAPLOOKAHEAD-1,stream->nmsgs);
  376.   char seq[20];
  377.   IMAPPARSEDREPLY *reply;
  378.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  379.   if (!elt->env) {        /* if we don't have an envelope */
  380.     if (msgno != stream->nmsgs)    /* never lookahead if last message */
  381.       while (i < j && !(mail_elt (stream,i+1)->env)) i++;
  382.     sprintf (seq,"%ld:%ld",msgno,i);
  383.     if (LOCAL->use_body) {    /* build "FETCH msgno:i FULL" if can do */
  384.       if (!(strcmp ((reply = imap_send (stream,"FETCH",seq,"FULL"))->key,
  385.             "BAD"))) LOCAL->use_body = NIL;
  386.     }
  387.                 /* can't, send "FETCH msgno:i ALL" instead */
  388.     if (!LOCAL->use_body) reply = imap_send (stream,"FETCH",seq,"ALL");
  389.     if (!imap_OK (stream,reply)) {
  390.       sprintf (LOCAL->tmp,"Fetch envelope rejected: %.80s",reply->text);
  391.       mm_log (LOCAL->tmp,ERROR);
  392.       return NIL;
  393.     }
  394.   }
  395.   return elt->env;        /* return the envelope */
  396. }
  397.  
  398. /* Mail Access Protocol fetch message header
  399.  * Accepts: MAIL stream
  400.  *        message # to fetch
  401.  * Returns: message header in RFC822 format
  402.  */
  403.  
  404. char *map_fetchheader (stream,msgno)
  405.     MAILSTREAM *stream;
  406.     long msgno;
  407. {
  408.   char seq[20];
  409.   long i = msgno - 1;
  410.   IMAPPARSEDREPLY *reply;
  411.   if (!LOCAL->header[i]) {    /* send "FETCH msgno RFC822.HEADER" */
  412.     sprintf (seq,"%ld",msgno);
  413.     if (!imap_OK (stream,reply = imap_send (stream,"FETCH",seq,LOCAL->text[i] ?
  414.                         "RFC822.HEADER" :
  415.                         "(RFC822.HEADER RFC822.TEXT)"))) {
  416.       sprintf (LOCAL->tmp,"Fetch header rejected: %.80s",reply->text);
  417.       mm_log (LOCAL->tmp,ERROR);
  418.     }
  419.   }
  420.   return LOCAL->header[i] ? LOCAL->header[i] : "";
  421. }
  422.  
  423.  
  424. /* Mail Access Protocol fetch message text (only)
  425.     body only;
  426.  * Accepts: MAIL stream
  427.  *        message # to fetch
  428.  * Returns: message text in RFC822 format
  429.  */
  430.  
  431. char *map_fetchtext (stream,msgno)
  432.     MAILSTREAM *stream;
  433.     long msgno;
  434. {
  435.   char seq[20];
  436.   long i = msgno - 1;
  437.   IMAPPARSEDREPLY *reply;
  438.   if (!LOCAL->text[i]) {    /* send "FETCH msgno RFC822.TEXT" */
  439.     sprintf (seq,"%ld",msgno);
  440.     if (!imap_OK (stream,reply = imap_send(stream,"FETCH",seq,"RFC822.TEXT"))){
  441.       sprintf (LOCAL->tmp,"Fetch text rejected: %.80s",reply->text);
  442.       mm_log (LOCAL->tmp,ERROR);
  443.     }
  444.   }
  445.   return LOCAL->text[i] ? LOCAL->text[i] : "";
  446. }
  447.  
  448. /* Mail Access Protocol fetch message body as a structure
  449.  * Accepts: Mail stream
  450.  *        message # to fetch
  451.  *        section specifier
  452.  *        pointer to length
  453.  * Returns: pointer to section of message body
  454.  */
  455.  
  456. char *map_fetchbody (stream,m,sec,len)
  457.     MAILSTREAM *stream;
  458.     long m;
  459.     char *sec;
  460.     unsigned long *len;
  461. {
  462.   BODY *b;
  463.   PART *pt;
  464.   char *s = sec;
  465.   char **ss;
  466.   unsigned long i;
  467.   char seq[40];
  468.   IMAPPARSEDREPLY *reply;
  469.   *len = 0;            /* in case failure */
  470.                 /* make sure have a body */
  471.   if (!(LOCAL->use_body && map_fetchenvelope (stream,m) &&
  472.     (b = mail_elt (stream,m)->body))) {
  473.                 /* bodies not supported, wanted section 1? */
  474.     if (strcmp (sec,"1")) return NIL;
  475.                 /* yes, return text */
  476.     *len = strlen (s = map_fetchtext (stream,m));
  477.     return s;
  478.   }
  479.   if (!(s && *s && ((i = strtol (s,&s,10)) > 0))) return NIL;
  480.   do {                /* until find desired body part */
  481.                 /* multipart content? */
  482.     if (b->type == TYPEMULTIPART) {
  483.       pt = b->contents.part;    /* yes, find desired part */
  484.       while (--i && (pt = pt->next));
  485.       if (!pt) return NIL;    /* bad specifier */
  486.                 /* note new body, check valid nesting */
  487.       if (((b = &pt->body)->type == TYPEMULTIPART) && !*s) return NIL;
  488.     }
  489.     else if (i != 1) return NIL;/* otherwise must be section 1 */
  490.                 /* need to go down further? */
  491.     if (i = *s) switch (b->type) {
  492.     case TYPEMESSAGE:        /* embedded message, calculate new base */
  493.       b = b->contents.msg.body;    /* get its body, drop into multipart case */
  494.     case TYPEMULTIPART:        /* multipart, get next section */
  495.       if ((*s++ == '.') && (i = strtol (s,&s,10)) > 0) break;
  496.     default:            /* bogus subpart specification */
  497.       return NIL;
  498.     }
  499.   } while (i);
  500.  
  501.                 /* lose if body bogus */
  502.   if ((!b) || b->type == TYPEMULTIPART) return NIL;
  503.   switch (b->type) {        /* decide where the data is based on type */
  504.   case TYPEMESSAGE:        /* encapsulated message */
  505.     ss = &b->contents.msg.text;
  506.     break;
  507.   case TYPETEXT:        /* textual data */
  508.     ss = (char **) &b->contents.text;
  509.     break;
  510.   default:            /* otherwise assume it is binary */
  511.     ss = (char **) &b->contents.binary;
  512.     break;
  513.   }
  514.   if (!*ss) {            /* fetch data if don't have it yet */
  515.     sprintf (seq,"%ld BODY[%s]",m,sec);
  516.     if (!imap_OK (stream,reply = imap_send (stream,"FETCH",seq,NIL))) {
  517.       sprintf (LOCAL->tmp,"Fetch body section rejected: %.80s",reply->text);
  518.       mm_log (LOCAL->tmp,ERROR);
  519.     }
  520.   }
  521.                 /* return data size if have data */
  522.   if (s = *ss) *len = b->size.bytes;
  523.   return s;
  524. }
  525.  
  526. /* Mail Access Protocol set flag
  527.  * Accepts: MAIL stream
  528.  *        sequence
  529.  *        flag(s)
  530.  */
  531.  
  532. void map_setflag (stream,sequence,flag)
  533.     MAILSTREAM *stream;
  534.     char *sequence;
  535.     char *flag;
  536. {
  537.   char flg[MAILTMPLEN];
  538.   IMAPPARSEDREPLY *reply;
  539.                 /* "STORE sequence +Flags flag" */
  540.   sprintf (flg,"+Flags %s",flag);
  541.   if (!imap_OK (stream,reply = imap_send (stream,"STORE",sequence,flg))) {
  542.     sprintf (LOCAL->tmp,"Set flag rejected: %.80s",reply->text);
  543.     mm_log (LOCAL->tmp,ERROR);
  544.   }
  545. }
  546.  
  547.  
  548. /* Mail Access Protocol clear flag
  549.  * Accepts: MAIL stream
  550.  *        sequence
  551.  *        flag(s)
  552.  */
  553.  
  554. void map_clearflag (stream,sequence,flag)
  555.     MAILSTREAM *stream;
  556.     char *sequence;
  557.     char *flag;
  558. {
  559.   char flg[MAILTMPLEN];
  560.   IMAPPARSEDREPLY *reply;
  561.                 /* "STORE sequence -Flags flag" */
  562.   sprintf (flg,"-Flags %s",flag);
  563.   if (!imap_OK (stream,reply = imap_send (stream,"STORE",sequence,flg))) {
  564.     sprintf (LOCAL->tmp,"Clear flag rejected: %.80s",reply->text);
  565.     mm_log (LOCAL->tmp,ERROR);
  566.   }
  567. }
  568.  
  569. /* Mail Access Protocol search for messages
  570.  * Accepts: MAIL stream
  571.  *        search criteria
  572.  */
  573.  
  574. void map_search (stream,criteria)
  575.     MAILSTREAM *stream;
  576.     char *criteria;
  577. {
  578.   long i,j;
  579.   char *s;
  580.   IMAPPARSEDREPLY *reply;
  581.   MESSAGECACHE *elt;
  582.                 /* do implicit CHECK, then SEARCH */
  583.   if (imap_OK (stream,reply = imap_send (stream,"CHECK",NIL,NIL)) &&
  584.       imap_OK (stream,reply = imap_send (stream,"SEARCH",criteria,NIL))) {
  585.     s = LOCAL->tmp;        /* build sequence in temporary buffer */
  586.     *s = '\0';            /* initially nothing */
  587.                 /* search through mailbox */
  588.     for (i = 1; i <= stream->nmsgs; ++i)
  589.                 /* for searched messages with no envelope */
  590.       if ((elt = mail_elt (stream,i)) && elt->searched && !elt->env) {
  591.                 /* prepend with comma if not first time */
  592.     if (LOCAL->tmp[0]) *s++ = ',';
  593.     sprintf (s,"%ld",j = i);/* output message number */
  594.     s += strlen (s);    /* point at end of string */
  595.                 /* search for possible end of range */
  596.     while (i < stream->nmsgs && (elt = mail_elt (stream,i+1)) &&
  597.            elt->searched && !elt->env) i++;
  598.     if (i != j) {        /* if a range */
  599.       sprintf (s,":%ld",i);    /* output delimiter and end of range */
  600.       s += strlen (s);    /* point at end of string */
  601.     }
  602.       }
  603.     if (LOCAL->tmp[0]) {    /* anything to pre-fetch? */
  604.       s = cpystr (LOCAL->tmp);    /* yes, copy sequence */
  605.       if (LOCAL->use_body) {    /* fetch bodies if can do */
  606.     if (!(strcmp ("BAD",    /* pre-fetch the searched messages */
  607.               (reply = imap_send (stream,"FETCH",s,"FULL"))->key)))
  608.       LOCAL->use_body = NIL;/* remember if we can't */
  609.       }
  610.       if (!LOCAL->use_body) reply = imap_send (stream,"FETCH",s,"ALL");
  611.       if (!imap_OK (stream,reply)) {
  612.     sprintf (LOCAL->tmp,"Search pre-fetch rejected: %.80s",reply->text);
  613.     mm_log (LOCAL->tmp,ERROR);
  614.       }
  615.       fs_give ((void **) &s);    /* flush copy of sequence */
  616.     }
  617.   }
  618.   else {
  619.     sprintf (LOCAL->tmp,"Search rejected: %.80s",reply->text);
  620.     mm_log (LOCAL->tmp,ERROR);
  621.   }
  622. }
  623.  
  624. /* Mail Access Protocol ping mailbox
  625.  * Accepts: MAIL stream
  626.  * Returns: T if stream still alive, else NIL
  627.  */
  628.  
  629. long map_ping (stream)
  630.     MAILSTREAM *stream;
  631. {
  632.   return (LOCAL->tcpstream &&    /* send "NOOP" */
  633.       imap_OK (stream,imap_send (stream,"NOOP",NIL,NIL))) ? T : NIL;
  634. }
  635.  
  636.  
  637. /* Mail Access Protocol check mailbox
  638.  * Accepts: MAIL stream
  639.  */
  640.  
  641. void map_check (stream)
  642.     MAILSTREAM *stream;
  643. {
  644.                 /* send "CHECK" */
  645.   IMAPPARSEDREPLY *reply = imap_send (stream,"CHECK",NIL,NIL);
  646.   if (!imap_OK (stream,reply)) {
  647.     sprintf (LOCAL->tmp,"Check rejected: %.80s",reply->text);
  648.     mm_log (LOCAL->tmp,ERROR);
  649.   }
  650.   else mm_log (reply->text,(long) NIL);
  651. }
  652.  
  653.  
  654. /* Mail Access Protocol expunge mailbox
  655.  * Accepts: MAIL stream
  656.  */
  657.  
  658. void map_expunge (stream)
  659.     MAILSTREAM *stream;
  660. {
  661.                 /* send "EXPUNGE" */
  662.   IMAPPARSEDREPLY *reply = imap_send (stream,"EXPUNGE",NIL,NIL);
  663.   if (!imap_OK (stream,reply)) {
  664.     sprintf (LOCAL->tmp,"Expunge rejected: %.80s",reply->text);
  665.     mm_log (LOCAL->tmp,ERROR);
  666.   }
  667.   else mm_log (reply->text,(long) NIL);
  668. }
  669.  
  670. /* Mail Access Protocol copy message(s)
  671.     s;
  672.  * Accepts: MAIL stream
  673.  *        sequence
  674.  *        destination mailbox
  675.  * Returns: T if successful else NIL
  676.  */
  677.  
  678. long map_copy (stream,sequence,mailbox)
  679.     MAILSTREAM *stream;
  680.     char *sequence;
  681.     char *mailbox;
  682. {
  683.   IMAPPARSEDREPLY *reply;
  684.   if (!LOCAL->tcpstream) {    /* not valid on dead stream */
  685.     mm_log ("Copy rejected: connection to remote IMAP server closed",ERROR);
  686.     return NIL;
  687.   }
  688.                 /* send "COPY sequence mailbox" */
  689.   if (!imap_OK (stream,reply = imap_send (stream,"COPY",sequence,mailbox))) {
  690.     sprintf (LOCAL->tmp,"Copy rejected: %.80s",reply->text);
  691.     mm_log (LOCAL->tmp,ERROR);
  692.     return NIL;
  693.   }
  694.   map_setflag (stream,sequence,"\\Seen");
  695.   return T;
  696. }
  697.  
  698.  
  699. /* Mail Access Protocol move message(s)
  700.     s;
  701.  * Accepts: MAIL stream
  702.  *        sequence
  703.  *        destination mailbox
  704.  * Returns: T if successful else NIL
  705.  */
  706.  
  707. long map_move (stream,sequence,mailbox)
  708.     MAILSTREAM *stream;
  709.     char *sequence;
  710.     char *mailbox;
  711. {
  712.   IMAPPARSEDREPLY *reply;
  713.   if (!LOCAL->tcpstream) {    /* not valid on dead stream */
  714.     mm_log ("Move rejected: connection to remote IMAP server closed",ERROR);
  715.     return NIL;
  716.   }
  717.                 /* send "COPY sequence mailbox" */
  718.   if (!imap_OK (stream,reply = imap_send (stream,"COPY",sequence,mailbox))) {
  719.     sprintf (LOCAL->tmp,"Move rejected: %.80s",reply->text);
  720.     mm_log (LOCAL->tmp,ERROR);
  721.     return NIL;
  722.   }
  723.   map_setflag (stream,sequence,"\\Deleted \\Seen");
  724.   return T;
  725. }
  726.  
  727. /* Mail Access Protocol garbage collect stream
  728.  * Accepts: Mail stream
  729.  *        garbage collection flags
  730.  */
  731.  
  732. void map_gc (stream,gcflags)
  733.     MAILSTREAM *stream;
  734.     long gcflags;
  735. {
  736.   unsigned long i = 0;
  737.   MESSAGECACHE *elt;
  738.   if (gcflags & GC_TEXTS)    /* garbage collect texts? */
  739.     while (i < stream->nmsgs) {    /* flush texts from cache */
  740.       if (LOCAL->header[i]) fs_give ((void **) &LOCAL->header[i]);
  741.       if (LOCAL->text[i]) fs_give ((void **) &LOCAL->text[i]);
  742.       if ((elt = stream->cache[i++])) map_gc_body (elt->body);
  743.     }
  744.   if (gcflags & GC_ELT)        /* garbage collect cache? */
  745.     for (i = 0; i < stream->nmsgs; i++)
  746.       if (stream->cache[i]) mail_free_elt (&stream->cache[i]);
  747. }
  748.  
  749.  
  750. /* Mail Access Protocol garbage collect body texts
  751.  * Accepts: body to GC
  752.  */
  753.  
  754. void map_gc_body (body)
  755.     BODY *body;
  756. {
  757.   PART *part;
  758.   if (body) switch (body->type) {
  759.   case TYPETEXT:        /* unformatted text */
  760.     if (body->contents.text) fs_give ((void **) &body->contents.text);
  761.     break;
  762.   case TYPEMULTIPART:        /* multiple part */
  763.     if (part = body->contents.part) do map_gc_body (&part->body);
  764.     while (part = part->next);
  765.     break;
  766.   case TYPEMESSAGE:        /* encapsulated message */
  767.     map_gc_body (body->contents.msg.body);
  768.     if (body->contents.msg.text)
  769.       fs_give ((void **) &body->contents.msg.text);
  770.     break;
  771.   case TYPEAPPLICATION:        /* application data */
  772.   case TYPEAUDIO:        /* audio */
  773.   case TYPEIMAGE:        /* static image */
  774.   case TYPEVIDEO:        /* video */
  775.     if (body->contents.binary) fs_give (&body->contents.binary);
  776.     break;
  777.   default:
  778.     break;
  779.   }
  780. }
  781.  
  782. /* Internal routines */
  783.  
  784.  
  785. /* Mail Access Protocol return host name field of "{host}mailbox"
  786.  * Accepts: "{host}mailbox" format name
  787.  * Returns: host name or NIL if error
  788.  */
  789.  
  790. char *imap_hostfield (string)
  791.     char *string;
  792. {
  793.   register char *start;
  794.   char c;
  795.   long i = 0;
  796.                 /* string must begin with "{" */
  797.   if (*string != '{') return NIL;
  798.   start = ++string;        /* save pointer to first host char */
  799.                 /* search for closing "}" */
  800.   while ((c = *string++) != '}') if (!c) return NIL;
  801.                 /* must have at least one char */
  802.   if (!(i = string - start - 1)) return NIL;
  803.   string = (char *) fs_get (i + 1);
  804.   strncpy (string,start,i);    /* copy the string */
  805.   string[i] = '\0';        /* tie off with null */
  806.   return string;
  807. }
  808.  
  809.  
  810. /* Mail Access Protocol return mailbox field of "{host}mailbox"
  811.  * Accepts: "{host}mailbox" format name
  812.  * Returns: mailbox name or NIL if error
  813.  */
  814.  
  815. char *imap_mailboxfield (string)
  816.     char *string;
  817. {
  818.   char c;
  819.                 /* string must begin with "{" */
  820.   if (*string != '{') return NIL;
  821.                 /* search for closing "}" */
  822.   while ((c = *string++) != '}') if (!c) return NIL;
  823.   return cpystr (strlen (string) ? string : "INBOX");
  824. }
  825.  
  826.  
  827. /* Mail Access Protocol return host name
  828.  * Accepts: MAIL stream
  829.  * Returns: host name
  830.  */
  831.  
  832. char *imap_host (stream)
  833.     MAILSTREAM *stream;
  834. {
  835.                 /* return host name on stream if open */
  836.   return (LOCAL && LOCAL->tcpstream) ? tcp_host (LOCAL->tcpstream) :
  837.     "<closed stream>";
  838. }
  839.  
  840. /* Mail Access Protocol send command
  841.  * Accepts: MAIL stream
  842.  *        command
  843.  *        command argument
  844.  * Returns: parsed reply
  845.  */
  846.  
  847. IMAPPARSEDREPLY *imap_send (stream,cmd,arg,arg2)
  848.     MAILSTREAM *stream;
  849.     char *cmd;
  850.     char *arg;
  851.     char *arg2;
  852. {
  853.   char tag[7];
  854.   IMAPPARSEDREPLY *reply;
  855.                   /* gensym a new tag */
  856.   sprintf (tag,"A%05ld",stream->gensym++);
  857.   if (!LOCAL->tcpstream) return imap_fake (stream,tag,"OK No-op dead stream");
  858.   mail_lock (stream);        /* lock up the stream */
  859.   if (arg2) sprintf (LOCAL->tmp,"%s %s %s %s",tag,cmd,arg,arg2);
  860.   else if (arg) sprintf (LOCAL->tmp,"%s %s %s",tag,cmd,arg);
  861.   else sprintf (LOCAL->tmp,"%s %s",tag,cmd);
  862.   if (stream->debug) mm_dlog (LOCAL->tmp);
  863.   strcat (LOCAL->tmp,"\015\012");
  864.                 /* send the command */
  865.   if (!(tcp_soutr (LOCAL->tcpstream,LOCAL->tmp))) {
  866.                 /* close TCP connection */
  867.     tcp_close (LOCAL->tcpstream);
  868.     LOCAL->tcpstream = NIL;
  869.     mail_unlock (stream);    /* unlock stream */
  870.     return imap_fake (stream,tag,"BYE IMAP connection broken in send");
  871.   }
  872.                 /* get reply from server */
  873.   reply = imap_reply (stream,tag);
  874.   mail_unlock (stream);        /* unlock stream */
  875.   return reply;            /* return reply to caller */
  876. }
  877.  
  878. /* Mail Access Protocol get reply
  879.  * Accepts: MAIL stream
  880.  *        tag to search or NIL if want a greeting
  881.  * Returns: parsed reply, never NIL
  882.  */
  883.  
  884. IMAPPARSEDREPLY *imap_reply (stream,tag)
  885.     MAILSTREAM *stream;
  886.     char *tag;
  887. {
  888.   IMAPPARSEDREPLY *reply;
  889.   while (LOCAL->tcpstream) {    /* parse reply from server */
  890.     if ((reply = imap_parse_reply (stream,tcp_getline (LOCAL->tcpstream))) &&
  891.     strcmp (reply->tag,"+")) {
  892.                 /* untagged response means unsolicited data */
  893.       if (!strcmp (reply->tag,"*")) {
  894.     imap_parse_unsolicited (stream,reply);
  895.     if (tag) continue;    /* waiting for a response */
  896.     return reply;        /* return greeting */
  897.       }
  898.       else {            /* tagged reponse, return if desired reply */
  899.     if (tag && !strcmp (tag,reply->tag)) return reply;
  900.                 /* report bogon */
  901.     sprintf (LOCAL->tmp,"Unexpected tagged response: %.80s %.80s %.80s",
  902.          reply->tag,reply->key,reply->text);
  903.     mm_log (LOCAL->tmp,WARN);
  904.       }
  905.     }
  906.   }
  907.   return imap_fake (stream,tag,"BYE IMAP connection broken in reply");
  908. }
  909.  
  910. /* Mail Access Protocol parse reply
  911.  * Accepts: MAIL stream
  912.  *        text of reply
  913.  * Returns: parsed reply, or NIL if can't parse at least a tag and key
  914.  */
  915.  
  916.  
  917. IMAPPARSEDREPLY *imap_parse_reply (stream,text)
  918.     MAILSTREAM *stream;
  919.     char *text;
  920. {
  921.   if (LOCAL->reply.line) fs_give ((void **) &LOCAL->reply.line);
  922.   if (!(LOCAL->reply.line = text)) {
  923.                 /* NIL text means the stream died */
  924.     tcp_close (LOCAL->tcpstream);
  925.     LOCAL->tcpstream = NIL;
  926.     return NIL;
  927.   }
  928.   if (stream->debug) mm_dlog (LOCAL->reply.line);
  929.   LOCAL->reply.key = NIL;    /* init fields in case error */
  930.   LOCAL->reply.text = NIL;
  931.                 /* parse separate tag, key, text */
  932.   if (!((LOCAL->reply.tag = (char *) strtok (LOCAL->reply.line," ")) &&
  933.     (LOCAL->reply.key = (char *) strtok (NIL," ")))) {
  934.                 /* determine what is missing */
  935.     if (!LOCAL->reply.tag) strcpy (LOCAL->tmp,"IMAP server sent a blank line");
  936.     else sprintf (LOCAL->tmp,"Missing IMAP reply key: %.80s",LOCAL->reply.tag);
  937.     mm_log (LOCAL->tmp,WARN);    /* pass up the barfage */
  938.     return NIL;            /* can't parse this text */
  939.   }
  940.   ucase (LOCAL->reply.key);    /* make sure key is upper case */
  941.                 /* get text as well, allow empty text */
  942.   if (!(LOCAL->reply.text = (char *) strtok (NIL,"\n")))
  943.     LOCAL->reply.text = LOCAL->reply.key + strlen (LOCAL->reply.key);
  944.   return &LOCAL->reply;        /* return parsed reply */
  945. }
  946.  
  947. /* Mail Access Protocol fake reply
  948.  * Accepts: MAIL stream
  949.  *        tag
  950.  *        text of fake reply
  951.  * Returns: parsed reply
  952.  */
  953.  
  954. IMAPPARSEDREPLY *imap_fake (stream,tag,text)
  955.     MAILSTREAM *stream;
  956.     char *tag;
  957.     char *text;
  958. {
  959.                 /* build fake reply string */
  960.   sprintf (LOCAL->tmp,"%s %s",tag,text);
  961.                 /* parse and return it */
  962.   return imap_parse_reply (stream,cpystr (LOCAL->tmp));
  963. }
  964.  
  965.  
  966. /* Mail Access Protocol check for OK response in tagged reply
  967.  * Accepts: MAIL stream
  968.  *        parsed reply
  969.  * Returns: T if OK else NIL
  970.  */
  971.  
  972. long imap_OK (stream,reply)
  973.     MAILSTREAM *stream;
  974.     IMAPPARSEDREPLY *reply;
  975. {
  976.                 /* OK - operation succeeded */
  977.   if (!strcmp (reply->key,"OK")) return T;
  978.                 /* NO - operation failed */
  979.   else if (strcmp (reply->key,"NO")) {
  980.                 /* BAD - operation rejected */
  981.     if (!strcmp (reply->key,"BAD"))
  982.       sprintf (LOCAL->tmp,"IMAP error: %.80s",reply->text);
  983.                 /* BYE - server is going away */
  984.     else if (!strcmp (reply->key,"BYE")) strcpy (LOCAL->tmp,reply->text);
  985.     else sprintf (LOCAL->tmp,"Unexpected IMAP response: %.80s %.80s",
  986.           reply->key,reply->text);
  987.     mm_log (LOCAL->tmp,WARN);    /* log the sucker */
  988.   }
  989.   return NIL;
  990. }
  991.  
  992. /* Mail Access Protocol parse and act upon unsolicited reply
  993.  * Accepts: MAIL stream
  994.  *        parsed reply
  995.  */
  996.  
  997. void imap_parse_unsolicited (stream,reply)
  998.     MAILSTREAM *stream;
  999.     IMAPPARSEDREPLY *reply;
  1000. {
  1001.   unsigned long i,j;
  1002.   long msgno;
  1003.   char *endptr;
  1004.   char *keyptr,*txtptr;
  1005.                 /* see if key is a number */
  1006.   msgno = strtol (reply->key,&endptr,10);
  1007.   if (*endptr) {        /* if non-numeric */
  1008.     if (!strcmp (reply->key,"FLAGS")) imap_parse_flaglst (stream,reply);
  1009.     else if (!strcmp (reply->key,"SEARCH")) imap_searched (stream,reply->text);
  1010.     else if (!strcmp (reply->key,"MAILBOX")) {
  1011.       sprintf (LOCAL->tmp,"{%s}%s",imap_host (stream),reply->text);
  1012.       mm_mailbox (((*reply->text != '{') ||
  1013.            (*(strchr (stream->mailbox,'}')+1) == '{')) ?
  1014.           LOCAL->tmp : reply->text);
  1015.     }
  1016.     else if (!strcmp (reply->key,"BBOARD")) {
  1017.       sprintf (LOCAL->tmp,"{%s}%s",imap_host (stream),reply->text);
  1018.       mm_bboard (((*reply->text != '{') ||
  1019.           (*(strchr (stream->mailbox,'}')+1) == '{')) ?
  1020.          LOCAL->tmp : reply->text);
  1021.     }
  1022.     else if (!strcmp (reply->key,"BYE")) {
  1023.       if (!stream->silent) mm_log (reply->text,(long) NIL);
  1024.     }
  1025.     else if (!strcmp (reply->key,"OK"))
  1026.       mm_notify (stream,reply->text,(long) NIL);
  1027.     else if (!strcmp (reply->key,"NO")) {
  1028.       if (!stream->silent) mm_notify (stream,reply->text,WARN);
  1029.     }
  1030.     else if (!strcmp (reply->key,"BAD")) mm_notify (stream,reply->text,ERROR);
  1031.     else {
  1032.       sprintf (LOCAL->tmp,"Unexpected unsolicited message: %.80s",reply->key);
  1033.       mm_log (LOCAL->tmp,WARN);
  1034.     }
  1035.   }
  1036.  
  1037.   else {            /* if numeric, a keyword follows */
  1038.                 /* deposit null at end of keyword */
  1039.     keyptr = ucase ((char *) strtok (reply->text," "));
  1040.                 /* and locate the text after it */
  1041.     txtptr = (char *) strtok (NIL,"\n");
  1042.                 /* now take the action */
  1043.                 /* change in size of mailbox */
  1044.     if (!strcmp (keyptr,"EXISTS")) {
  1045.       if (msgno > LOCAL->cachesize) {
  1046.     i = (j = msgno + CACHEINCREMENT) * sizeof (char *);
  1047.     if (LOCAL->cachesize) {    /* resize extant caches */
  1048.       fs_resize ((void **) &LOCAL->header,i);
  1049.       fs_resize ((void **) &LOCAL->text,i);
  1050.     }
  1051.     else {            /* instantiate new caches */
  1052.       LOCAL->header = (char **) fs_get (i);
  1053.       LOCAL->text = (char **) fs_get (i);
  1054.     }
  1055.                 /* init new space in caches */
  1056.     for (i = LOCAL->cachesize; i < j; i++)
  1057.       LOCAL->header[i] = LOCAL->text[i] = NIL;
  1058.     LOCAL->cachesize = j;
  1059.       }
  1060.       mail_exists (stream,msgno);
  1061.     }
  1062.     else if (!strcmp (keyptr,"RECENT")) mail_recent (stream,msgno);
  1063.     else if (!strcmp (keyptr,"EXPUNGE")) imap_expunged (stream,msgno);
  1064.     else if (!strcmp (keyptr,"FETCH"))
  1065.       imap_parse_data (stream,msgno,txtptr,reply);
  1066.                 /* obsolete alias for FETCH */
  1067.     else if (!strcmp (keyptr,"STORE"))
  1068.       imap_parse_data (stream,msgno,txtptr,reply);
  1069.                 /* obsolete response to COPY */
  1070.     else if (strcmp (keyptr,"COPY")) {
  1071.       sprintf (LOCAL->tmp,"Unknown message data: %ld %.80s",msgno,keyptr);
  1072.       mm_log (LOCAL->tmp,WARN);
  1073.     }
  1074.   }
  1075. }
  1076.  
  1077. /* Mail Access Protocol parse flag list
  1078.  * Accepts: MAIL stream
  1079.  *        parsed reply
  1080.  *
  1081.  *  The reply->line is yanked out of the parsed reply and stored on
  1082.  * stream->flagstring.  This is the original fs_get'd reply string, and
  1083.  * has all the flagstrings embedded in it.
  1084.  */
  1085.  
  1086. void imap_parse_flaglst (stream,reply)
  1087.     MAILSTREAM *stream;
  1088.     IMAPPARSEDREPLY *reply;
  1089. {
  1090.   char *text = reply->text;
  1091.   char *flag;
  1092.   long i;
  1093.                 /* flush old flagstring and flags if any */
  1094.   if (stream->flagstring) fs_give ((void **) &stream->flagstring);
  1095.   for (i = 0; i < NUSERFLAGS; ++i) stream->user_flags[i] = NIL;
  1096.                 /* remember this new one */
  1097.   stream->flagstring = reply->line;
  1098.   reply->line = NIL;
  1099.   ++text;            /* skip past open parenthesis */
  1100.                 /* get first flag if any */
  1101.   if (flag = (char *) strtok (text," )")) {
  1102.     i = 0;            /* init flag index */
  1103.                 /* add all user flags */
  1104.     do if (*flag != '\\') stream->user_flags[i++] = flag;
  1105.       while (flag = (char *) strtok (NIL," )"));
  1106.   }
  1107. }
  1108.  
  1109.  
  1110. /* Mail Access Protocol messages have been searched out
  1111.  * Accepts: MAIL stream
  1112.  *        list of message numbers
  1113.  *
  1114.  * Calls external "mail_searched" function to notify main program
  1115.  */
  1116.  
  1117. void imap_searched (stream,text)
  1118.     MAILSTREAM *stream;
  1119.     char *text;
  1120. {
  1121.                 /* only do something if have text */
  1122.   if (text && (text = (char *) strtok (text," ")))
  1123.     for (; text; text = (char *) strtok (NIL," "))
  1124.       mail_searched (stream,atol (text));
  1125. }
  1126.  
  1127. /* Mail Access Protocol message has been expunged
  1128.  * Accepts: MAIL stream
  1129.  *        message number
  1130.  *
  1131.  * Calls external "mail_searched" function to notify main program
  1132.  */
  1133.  
  1134. void imap_expunged (stream,msgno)
  1135.     MAILSTREAM *stream;
  1136.     long msgno;
  1137. {
  1138.   long i = msgno - 1;
  1139.   if (LOCAL->header[i]) fs_give ((void **) &LOCAL->header[i]);
  1140.   if (LOCAL->text[i]) fs_give ((void **) &LOCAL->text[i]);
  1141.                 /* slide down remainder of cache */
  1142.   for (i = msgno; i <= stream->nmsgs; ++i) {
  1143.     LOCAL->header[i - 1] = LOCAL->header[i];
  1144.     LOCAL->text[i - 1] = LOCAL->text[i];
  1145.   }
  1146.   LOCAL->header[i] = LOCAL->text[i] = NIL;
  1147.   mail_expunged (stream,msgno);    /* notify upper level */
  1148. }
  1149.  
  1150.  
  1151. /* Mail Access Protocol parse data
  1152.  * Accepts: MAIL stream
  1153.  *        message #
  1154.  *        text to parse
  1155.  *        parsed reply
  1156.  *
  1157.  *  This code should probably be made a bit more paranoid about malformed
  1158.  * S-expressions.
  1159.  */
  1160.  
  1161. void imap_parse_data (stream,msgno,text,reply)
  1162.     MAILSTREAM *stream;
  1163.     long msgno;
  1164.     char *text;
  1165.                    IMAPPARSEDREPLY *reply;
  1166. {
  1167.   char *prop;
  1168.   MESSAGECACHE *elt = mail_elt (stream,msgno);
  1169.   ++text;            /* skip past open parenthesis */
  1170.                 /* parse Lisp-form property list */
  1171.   while (prop = (char *) strtok (text," )")) {
  1172.                 /* point at value */
  1173.     text = (char *) strtok (NIL,"\n");
  1174.                 /* parse the property and its value */
  1175.     imap_parse_prop (stream,elt,ucase (prop),&text,reply);
  1176.   }
  1177. }
  1178.  
  1179. /* Mail Access Protocol parse property
  1180.  * Accepts: MAIL stream
  1181.  *        cache item
  1182.  *        property name
  1183.  *        property value text pointer
  1184.  *        parsed reply
  1185.  */
  1186.  
  1187. void imap_parse_prop (stream,elt,prop,txtptr,reply)
  1188.     MAILSTREAM *stream;
  1189.     MESSAGECACHE *elt;
  1190.     char *prop;
  1191.                    char **txtptr;
  1192.     IMAPPARSEDREPLY *reply;
  1193. {
  1194.   char *s;
  1195.   long i = elt->msgno - 1;
  1196.   if (!strcmp (prop,"ENVELOPE"))
  1197.     imap_parse_envelope (stream,&elt->env,txtptr,reply);
  1198.   else if (!strcmp (prop,"FLAGS")) imap_parse_flags (stream,elt,txtptr);
  1199.   else if (!strcmp (prop,"INTERNALDATE")) {
  1200.     if (elt->internal_date) fs_give ((void **) &elt->internal_date);
  1201.     elt->internal_date = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1202.   }
  1203.   else if (!strcmp (prop,"RFC822.HEADER")) {
  1204.     if (LOCAL->header[i]) fs_give ((void **) &LOCAL->header[i]);
  1205.     LOCAL->header[i] = imap_parse_string (stream,txtptr,reply,elt->msgno);
  1206.   }
  1207.   else if (!strcmp (prop,"RFC822.SIZE"))
  1208.     elt->rfc822_size = imap_parse_number (stream,txtptr);
  1209.   else if (!strcmp (prop,"RFC822.TEXT")) {
  1210.     if (LOCAL->text[i]) fs_give ((void **) &LOCAL->text[i]);
  1211.     LOCAL->text[i] = imap_parse_string (stream,txtptr,reply,elt->msgno);
  1212.   }
  1213.   else if (prop[0] == 'B' && prop[1] == 'O' && prop[2] == 'D' &&
  1214.        prop[3] == 'Y') {
  1215.     s = cpystr (prop+4);    /* copy segment specifier */
  1216.     imap_parse_body (stream,elt->msgno,&elt->body,s,txtptr,reply);
  1217.     fs_give ((void **) &s);
  1218.   }
  1219.                 /* this shouldn't happen with our client */
  1220.   else if (!strcmp (prop,"RFC822")) {
  1221.     if (LOCAL->text[i]) fs_give ((void **) &LOCAL->text[i]);
  1222.     LOCAL->text[i] = imap_parse_string (stream,txtptr,reply,elt->msgno);
  1223.   }
  1224.   else {
  1225.     sprintf (LOCAL->tmp,"Unknown message property: %.80s",prop);
  1226.     mm_log (LOCAL->tmp,WARN);
  1227.   }
  1228. }
  1229.  
  1230. /* Mail Access Protocol parse envelope
  1231.  * Accepts: MAIL stream
  1232.  *        pointer to envelope pointer
  1233.  *        current text pointer
  1234.  *        parsed reply
  1235.  *
  1236.  * Updates text pointer
  1237.  */
  1238.  
  1239. void imap_parse_envelope (stream,env,txtptr,reply)
  1240.     MAILSTREAM *stream;
  1241.     ENVELOPE **env;
  1242.     char **txtptr;
  1243.                    IMAPPARSEDREPLY *reply;
  1244. {
  1245.   char c = *((*txtptr)++);    /* grab first character */
  1246.                 /* ignore leading spaces */
  1247.   while (c == ' ') c = *((*txtptr)++);
  1248.                 /* free any old envelope */
  1249.   if (*env) mail_free_envelope (env);
  1250.   switch (c) {            /* dispatch on first character */
  1251.   case '(':            /* if envelope S-expression */
  1252.     *env = mail_newenvelope ();    /* parse the new envelope */
  1253.     (*env)->date = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1254.     (*env)->subject = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1255.     (*env)->from = imap_parse_adrlist (stream,txtptr,reply);
  1256.     (*env)->sender = imap_parse_adrlist (stream,txtptr,reply);
  1257.     (*env)->reply_to = imap_parse_adrlist (stream,txtptr,reply);
  1258.     (*env)->to = imap_parse_adrlist (stream,txtptr,reply);
  1259.     (*env)->cc = imap_parse_adrlist (stream,txtptr,reply);
  1260.     (*env)->bcc = imap_parse_adrlist (stream,txtptr,reply);
  1261.     (*env)->in_reply_to = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1262.     (*env)->message_id = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1263.     if (**txtptr != ')') {
  1264.       sprintf (LOCAL->tmp,"Junk at end of envelope: %.80s",*txtptr);
  1265.       mm_log (LOCAL->tmp,WARN);
  1266.     }
  1267.     else ++*txtptr;        /* skip past delimiter */
  1268.     break;
  1269.   case 'N':            /* if NIL */
  1270.   case 'n':
  1271.     ++*txtptr;            /* bump past "I" */
  1272.     ++*txtptr;            /* bump past "L" */
  1273.     break;
  1274.   default:
  1275.     sprintf (LOCAL->tmp,"Not an envelope: %.80s",*txtptr);
  1276.     mm_log (LOCAL->tmp,WARN);
  1277.     break;
  1278.   }
  1279. }
  1280.  
  1281. /* Mail Access Protocol parse address list
  1282.  * Accepts: MAIL stream
  1283.  *        current text pointer
  1284.  *        parsed reply
  1285.  * Returns: address list, NIL on failure
  1286.  *
  1287.  * Updates text pointer
  1288.  */
  1289.  
  1290. ADDRESS *imap_parse_adrlist (stream,txtptr,reply)
  1291.     MAILSTREAM *stream;
  1292.     char **txtptr;
  1293.                       IMAPPARSEDREPLY *reply;
  1294. {
  1295.   ADDRESS *adr = NIL;
  1296.   char c = **txtptr;        /* sniff at first character */
  1297.                 /* ignore leading spaces */
  1298.   while (c == ' ') c = *++*txtptr;
  1299.   ++*txtptr;            /* skip past open paren */
  1300.   switch (c) {
  1301.   case '(':            /* if envelope S-expression */
  1302.     adr = imap_parse_address (stream,txtptr,reply);
  1303.     if (**txtptr != ')') {
  1304.       sprintf (LOCAL->tmp,"Junk at end of address list: %.80s",*txtptr);
  1305.       mm_log (LOCAL->tmp,WARN);
  1306.     }
  1307.     else ++*txtptr;        /* skip past delimiter */
  1308.     break;
  1309.   case 'N':            /* if NIL */
  1310.   case 'n':
  1311.     ++*txtptr;            /* bump past "I" */
  1312.     ++*txtptr;            /* bump past "L" */
  1313.     break;
  1314.   default:
  1315.     sprintf (LOCAL->tmp,"Not an address: %.80s",*txtptr);
  1316.     mm_log (LOCAL->tmp,WARN);
  1317.     break;
  1318.   }
  1319.   return adr;
  1320. }
  1321.  
  1322. /* Mail Access Protocol parse address
  1323.  * Accepts: MAIL stream
  1324.  *        current text pointer
  1325.  *        parsed reply
  1326.  * Returns: address, NIL on failure
  1327.  *
  1328.  * Updates text pointer
  1329.  */
  1330.  
  1331. ADDRESS *imap_parse_address (stream,txtptr,reply)
  1332.     MAILSTREAM *stream;
  1333.     char **txtptr;
  1334.                       IMAPPARSEDREPLY *reply;
  1335. {
  1336.   ADDRESS *adr = NIL;
  1337.   ADDRESS *ret = NIL;
  1338.   ADDRESS *prev = NIL;
  1339.   char c = **txtptr;        /* sniff at first address character */
  1340.   switch (c) {
  1341.   case '(':            /* if envelope S-expression */
  1342.     while (c == '(') {        /* recursion dies on small stack machines */
  1343.       ++*txtptr;        /* skip past open paren */
  1344.       if (adr) prev = adr;    /* note previous if any */
  1345.       adr = mail_newaddr ();    /* instantiate address and parse its fields */
  1346.       adr->personal = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1347.       adr->adl = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1348.       adr->mailbox = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1349.       adr->host = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1350.       if (**txtptr != ')') {    /* handle trailing paren */
  1351.     sprintf (LOCAL->tmp,"Junk at end of address: %.80s",*txtptr);
  1352.     mm_log (LOCAL->tmp,WARN);
  1353.       }
  1354.       else ++*txtptr;        /* skip past close paren */
  1355.       c = **txtptr;        /* set up for while test */
  1356.                 /* ignore leading spaces in front of next */
  1357.       while (c == ' ') c = *++*txtptr;
  1358.       if (adr->mailbox) {    /* make sure address has a mailbox */
  1359.                 /* if no host default to server */
  1360.     if (!adr->host) adr->host = cpystr (imap_host (stream));
  1361.     if (!ret) ret = adr;    /* if first time note first adr */
  1362.                 /* if previous link new block to it */
  1363.     if (prev) prev->next = adr;
  1364.       }
  1365.       else {            /* server return sick address, flush it */
  1366.     mail_free_address (&adr);
  1367.     adr = NIL;        /* get a new one next time through the loop */
  1368.       }
  1369.     }
  1370.     break;
  1371.  
  1372.   case 'N':            /* if NIL */
  1373.   case 'n':
  1374.     *txtptr += 3;        /* bump past NIL */
  1375.     break;
  1376.   default:
  1377.     sprintf (LOCAL->tmp,"Not an address: %.80s",*txtptr);
  1378.     mm_log (LOCAL->tmp,WARN);
  1379.     break;
  1380.   }
  1381.   return ret;
  1382. }
  1383.  
  1384. /* Mail Access Protocol parse flags
  1385.  * Accepts: current message cache
  1386.  *        current text pointer
  1387.  *
  1388.  * Updates text pointer
  1389.  */
  1390.  
  1391. void imap_parse_flags (stream,elt,txtptr)
  1392.     MAILSTREAM *stream;
  1393.     MESSAGECACHE *elt;
  1394.     char **txtptr;
  1395. {
  1396.   char *flag;
  1397.   char c;
  1398.   elt->user_flags = NIL;    /* zap old flag values */
  1399.   elt->seen = elt->deleted = elt->flagged = elt->answered = elt->recent = NIL;
  1400.   while (T) {            /* parse list of flags */
  1401.     flag = ++*txtptr;        /* point at a flag */
  1402.                 /* scan for end of flag */
  1403.     while (**txtptr != ' ' && **txtptr != ')') ++*txtptr;
  1404.     c = **txtptr;        /* save delimiter */
  1405.     **txtptr = '\0';        /* tie off flag */
  1406.     if (*flag != '\0') {    /* if flag is non-null */
  1407.       if (*flag == '\\') {    /* if starts with \ must be sys flag */
  1408.     if (!strcmp (ucase (flag),"\\SEEN")) elt->seen = T;
  1409.     else if (!strcmp (flag,"\\DELETED")) elt->deleted = T;
  1410.     else if (!strcmp (flag,"\\FLAGGED")) elt->flagged = T;
  1411.     else if (!strcmp (flag,"\\ANSWERED")) elt->answered = T;
  1412.     else if (!strcmp (flag,"\\RECENT")) elt->recent = T;
  1413.                 /* coddle TOPS-20 server */
  1414.     else if (strcmp (flag,"\\XXXX") && strcmp (flag,"\\YYYY") &&
  1415.          strncmp (flag,"\\UNDEFINEDFLAG",14)) {
  1416.       sprintf (LOCAL->tmp,"Unknown system flag: %.80s",flag);
  1417.       mm_log (LOCAL->tmp,WARN);
  1418.     }
  1419.       }
  1420.                 /* otherwise user flag */
  1421.       else imap_parse_user_flag (stream,elt,flag);
  1422.     }
  1423.     if (c == ')') break;    /* quit if end of list */
  1424.   }
  1425.   ++*txtptr;            /* bump past delimiter */
  1426. }
  1427.  
  1428. /* Mail Access Protocol parse user flag
  1429.  * Accepts: message cache element
  1430.  *        flag name
  1431.  */
  1432.  
  1433. void imap_parse_user_flag (stream,elt,flag)
  1434.     MAILSTREAM *stream;
  1435.     MESSAGECACHE *elt;
  1436.     char *flag;
  1437. {
  1438.   long i;
  1439.                   /* sniff through all user flags */
  1440.   for (i = 0; i < NUSERFLAGS; ++i)
  1441.                   /* match this one? */
  1442.     if (!strcmp (flag,stream->user_flags[i])) {
  1443.       elt->user_flags |= 1 << i;/* yes, set the bit for that flag */
  1444.       return;            /* and quit */
  1445.     }
  1446.   sprintf (LOCAL->tmp,"Unknown user flag: %.80s",flag);
  1447.   mm_log (LOCAL->tmp,WARN);
  1448. }
  1449.  
  1450. /* Mail Access Protocol parse string
  1451.  * Accepts: MAIL stream
  1452.  *        current text pointer
  1453.  *        parsed reply
  1454.  *        flag that it may be kept outside of free storage cache
  1455.  * Returns: string
  1456.  *
  1457.  * Updates text pointer
  1458.  */
  1459.  
  1460. char *imap_parse_string (stream,txtptr,reply,special)
  1461.     MAILSTREAM *stream;
  1462.     char **txtptr;
  1463.                   IMAPPARSEDREPLY *reply;
  1464.     long special;
  1465. {
  1466.   char *st;
  1467.   char *string = NIL;
  1468.   unsigned long i,j;
  1469.   char c = **txtptr;        /* sniff at first character */
  1470.                 /* ignore leading spaces */
  1471.   while (c == ' ') c = *++*txtptr;
  1472.   st = ++*txtptr;        /* remember start of string */
  1473.   switch (c) {
  1474.   case '"':            /* if quoted string */
  1475.     i = 1;            /* initial byte count */
  1476.     while (**txtptr != '"') {    /* search for end of string */
  1477.       ++i;            /* bump count */
  1478.       ++*txtptr;        /* bump pointer */
  1479.     }
  1480.     **txtptr = '\0';        /* tie off string */
  1481.     string = (char *) fs_get (i);
  1482.     strncpy (string,st,i);    /* copy the string */
  1483.     ++*txtptr;            /* bump past delimiter */
  1484.     break;
  1485.   case 'N':            /* if NIL */
  1486.   case 'n':
  1487.     ++*txtptr;            /* bump past "I" */
  1488.     ++*txtptr;            /* bump past "L" */
  1489.     break;
  1490.  
  1491.   case '{':            /* if literal string */
  1492.                 /* get size of string */
  1493.     i = imap_parse_number (stream,txtptr);
  1494.     if (special && mailgets)    /* have special routine to slurp string? */
  1495.       string = (*mailgets) (tcp_getbuffer,LOCAL->tcpstream,i);
  1496.     else {            /* must slurp into free storage */
  1497.       string = (char *) fs_get (i + 1);
  1498.       *string = '\0';        /* init in case getbuffer fails */
  1499.                 /* get the literal */
  1500.       tcp_getbuffer (LOCAL->tcpstream,i,string);
  1501.     }
  1502.     fs_give ((void **) &reply->line);
  1503.                 /* get new reply text line */
  1504.     reply->line = tcp_getline (LOCAL->tcpstream);
  1505.     if (stream->debug) mm_dlog (reply->line);
  1506.     *txtptr = reply->line;    /* set text pointer to point at it */
  1507.     break;
  1508.   default:
  1509.     sprintf (LOCAL->tmp,"Not a string: %c%.80s",c,*txtptr);
  1510.     mm_log (LOCAL->tmp,WARN);
  1511.     break;
  1512.   }
  1513.   return string;
  1514. }
  1515.  
  1516.  
  1517. /* Mail Access Protocol parse number
  1518.  * Accepts: MAIL stream
  1519.  *        current text pointer
  1520.  * Returns: number parsed
  1521.  *
  1522.  * Updates text pointer
  1523.  */
  1524.  
  1525. unsigned long imap_parse_number (stream,txtptr)
  1526.     MAILSTREAM *stream;
  1527.     char **txtptr;
  1528. {                /* parse number */
  1529.   long i = strtol (*txtptr,txtptr,10);
  1530.   if (i < 0) {            /* number valid? */
  1531.     sprintf (LOCAL->tmp,"Bad number: %ld",i);
  1532.     mm_log (LOCAL->tmp,WARN);
  1533.     i = 0;            /* make sure valid */
  1534.   }
  1535.   return (unsigned long) i;
  1536. }
  1537.  
  1538. /* Mail Access Protocol parse body structure or contents
  1539.  * Accepts: MAIL stream
  1540.  *        pointer to body pointer
  1541.  *        pointer to segment
  1542.  *        current text pointer
  1543.  *        parsed reply
  1544.  *
  1545.  * Updates text pointer, stores body
  1546.  */
  1547.  
  1548. void imap_parse_body (stream,msgno,body,seg,txtptr,reply)
  1549.     MAILSTREAM *stream;
  1550.     long msgno;
  1551.     BODY **body;
  1552.     char *seg;
  1553.                    char **txtptr;
  1554.     IMAPPARSEDREPLY *reply;
  1555. {
  1556.   char *s;
  1557.   unsigned long i;
  1558.   BODY *b;
  1559.   PART *part;
  1560.   switch (*seg++) {        /* dispatch based on type of data */
  1561.   case '\0':            /* body structure */
  1562.     mail_free_body (body);    /* flush any prior body */
  1563.                 /* instantiate and parse a new body */
  1564.     imap_parse_body_structure (stream,*body = mail_newbody (),txtptr,reply);
  1565.     break;
  1566.   case '[':            /* body section text */
  1567.     if ((!(s = strchr (seg,']'))) || s[1]) {
  1568.       sprintf (LOCAL->tmp,"Bad body index: %.80s",seg);
  1569.       mm_log (LOCAL->tmp,WARN);
  1570.       return;
  1571.     }
  1572.     *s = '\0';            /* tie off section specifier */
  1573.                 /* get the body section text */
  1574.     s = imap_parse_string (stream,txtptr,reply,msgno);
  1575.     if (!(b = *body)) {        /* must have structure first */
  1576.       mm_log ("Body contents received when body structure unknown",WARN);
  1577.       fs_give ((void **) &s);
  1578.       return;
  1579.     }
  1580.                 /* get first section number */
  1581.     if (!(seg && *seg && ((i = strtol (seg,&seg,10)) > 0))) {
  1582.       mm_log ("Bogus section number",WARN);
  1583.       fs_give ((void **) &s);
  1584.       return;
  1585.     }
  1586.  
  1587.     do {            /* multipart content? */
  1588.       if (b->type == TYPEMULTIPART) {
  1589.     part = b->contents.part;/* yes, find desired part */
  1590.     while (--i && (part = part->next));
  1591.     if (!part || (((b = &part->body)->type == TYPEMULTIPART) && !*s)) {
  1592.       mm_log ("Bad section number",WARN);
  1593.       fs_give ((void **) &s);
  1594.       return;
  1595.     }
  1596.       }
  1597.       else if (i != 1) {    /* otherwise must be section 1 */
  1598.     mm_log ("Invalid section number",WARN);
  1599.     fs_give ((void **) &s);
  1600.     return;
  1601.       }
  1602.                 /* need to go down further? */
  1603.       if (i = *seg) switch (b->type) {
  1604.       case TYPEMESSAGE:        /* embedded message, get body */
  1605.     b = b->contents.msg.body;
  1606.       case TYPEMULTIPART:    /* multipart, get next section */
  1607.     if ((*seg++ == '.') && (i = strtol (seg,&seg,10)) > 0) break;
  1608.       default:            /* bogus subpart */
  1609.     mm_log ("Invalid sub-section",WARN);
  1610.     fs_give ((void **) &s);
  1611.     return;
  1612.       }
  1613.     } while (i);
  1614.     if (b) switch (b->type) {    /* decide where the data goes based on type */
  1615.     case TYPEMULTIPART:        /* nothing to fetch with these */
  1616.       mm_log ("Textual body contents received for MULTIPART body part",WARN);
  1617.       fs_give ((void **) &s);
  1618.       return;
  1619.     case TYPEMESSAGE:        /* encapsulated message */
  1620.       fs_give ((void **) &b->contents.msg.text);
  1621.       b->contents.msg.text = s;
  1622.       break;
  1623.     case TYPETEXT:        /* textual data */
  1624.       fs_give ((void **) &b->contents.text);
  1625.       b->contents.text = (unsigned char *) s;
  1626.       break;
  1627.     default:            /* otherwise assume it is binary */
  1628.       fs_give ((void **) &b->contents.binary);
  1629.       b->contents.binary = (void *) s;
  1630.       break;
  1631.     }
  1632.     break;
  1633.   default:            /* bogon */
  1634.     sprintf (LOCAL->tmp,"Bad body fetch: %.80s",seg);
  1635.     mm_log (LOCAL->tmp,WARN);
  1636.     return;
  1637.   }
  1638. }
  1639.  
  1640. /* Mail Access Protocol parse body structure
  1641.  * Accepts: MAIL stream
  1642.  *        current text pointer
  1643.  *        parsed reply
  1644.  *        body structure to write into
  1645.  *
  1646.  * Updates text pointer
  1647.  */
  1648.  
  1649. void imap_parse_body_structure (stream,body,txtptr,reply)
  1650.     MAILSTREAM *stream;
  1651.     BODY *body;
  1652.     char **txtptr;
  1653.                      IMAPPARSEDREPLY *reply;
  1654. {
  1655.   char *s;
  1656.   PART *part = NIL;
  1657.   PARAMETER *param = NIL;
  1658.   char c = *((*txtptr)++);    /* grab first character */
  1659.                 /* ignore leading spaces */
  1660.   while (c == ' ') c = *((*txtptr)++);
  1661.   switch (c) {            /* dispatch on first character */
  1662.   case '(':            /* body structure list */
  1663.     if (**txtptr == '(') {    /* multipart body? */
  1664.       body->type= TYPEMULTIPART;/* yes, set its type */
  1665.       do {            /* instantiate new body part */
  1666.     if (part) part = part->next = mail_newbody_part ();
  1667.     else body->contents.part = part = mail_newbody_part ();
  1668.                 /* parse it */
  1669.     imap_parse_body_structure (stream,&part->body,txtptr,reply);
  1670.       } while (**txtptr == '(');/* for each body part */
  1671.       if (!(body->subtype = imap_parse_string (stream,txtptr,reply,(long)NIL)))
  1672.     mm_log ("Missing multipart subtype",WARN);
  1673.       if (**txtptr != ')') {    /* validate ending */
  1674.     sprintf (LOCAL->tmp,"Junk at end of multipart body: %.80s",*txtptr);
  1675.     mm_log (LOCAL->tmp,WARN);
  1676.       }
  1677.       else ++*txtptr;        /* skip past delimiter */
  1678.     }
  1679.  
  1680.     else {            /* not multipart, parse type name */
  1681.       if (**txtptr == ')') {    /* empty body? */
  1682.     ++*txtptr;        /* bump past it */
  1683.     break;            /* and punt */
  1684.       }
  1685.       body->type = TYPEOTHER;    /* assume unknown type */
  1686.       body->encoding = ENCOTHER;/* and unknown encoding */
  1687.                 /* parse type */
  1688.       if (s = imap_parse_string (stream,txtptr,reply,(long) NIL)) {
  1689.     ucase (s);        /* make parse easier */
  1690.     switch (*s++) {        /* dispatch based on type */
  1691.     case 'A':        /* APPLICATION or AUDIO */
  1692.       if (!strcmp (s,"PPLICATION")) body->type = TYPEAPPLICATION;
  1693.       else if (!strcmp (s,"UDIO")) body->type = TYPEAUDIO;
  1694.       break;
  1695.     case 'I':        /* IMAGE */
  1696.       if (!strcmp (s,"MAGE")) body->type = TYPEIMAGE;
  1697.       break;
  1698.     case 'M':        /* MESSAGE */
  1699.       if (!strcmp (s,"ESSAGE")) body->type = TYPEMESSAGE;
  1700.       break;
  1701.     case 'T':        /* TEXT */
  1702.       if (!strcmp (s,"EXT")) body->type = TYPETEXT;
  1703.       break;
  1704.     case 'V':        /* VIDEO */
  1705.       if (!strcmp (s,"IDEO")) body->type = TYPEVIDEO;
  1706.       break;
  1707.     default:
  1708.       break;
  1709.     }
  1710.       }
  1711.                 /* parse subtype */
  1712.       body->subtype = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1713.  
  1714.       body->parameter = NIL;    /* init parameter list */
  1715.  
  1716.       c = *(*txtptr)++;        /* sniff at first character */
  1717.                 /* ignore leading spaces */
  1718.       while (c == ' ') c = *(*txtptr)++;
  1719.                 /* parse parameter list */
  1720.       if (c == '(') while (c != ')') {
  1721.     if (body->parameter)    /* append new parameter to tail */
  1722.       param = param->next = mail_newbody_parameter ();
  1723.     else body->parameter = param = mail_newbody_parameter ();
  1724.     if (!(param->attribute = imap_parse_string (stream,txtptr,reply,
  1725.                             (long) NIL))){
  1726.       mm_log ("Missing parameter attribute",WARN);
  1727.       break;
  1728.     }
  1729.     if (!(param->value = imap_parse_string (stream,txtptr,reply,
  1730.                         (long) NIL))) {
  1731.       sprintf (LOCAL->tmp,"Missing value for parameter %.80s",
  1732.            param->attribute);
  1733.       mm_log (LOCAL->tmp,WARN);
  1734.       break;
  1735.     }
  1736.     switch (c = **txtptr) {    /* see what comes after */
  1737.     case ' ':        /* flush whitespace */
  1738.       while ((c = *++*txtptr) == ' ');
  1739.       break;
  1740.     case ')':        /* end of attribute/value pairs */
  1741.       ++*txtptr;        /* skip past closing paren */
  1742.       break;
  1743.     default:
  1744.       sprintf (LOCAL->tmp,"Junk at end of parameter: %.80s",s);
  1745.       mm_log (LOCAL->tmp,WARN);
  1746.       break;
  1747.     }
  1748.       }
  1749.       else {            /* empty parameter, must be NIL */
  1750.     if (((c == 'N') || (c == 'n')) &&
  1751.         ((*(s = *txtptr) == 'I') || (*s == 'i')) &&
  1752.         ((s[1] == 'L') || (s[1] == 'l')) && (s[2] == ' ')) *txtptr += 2;
  1753.     else {
  1754.       sprintf (LOCAL->tmp,"Bogus body parameter: %c%.80s",c,s);
  1755.       mm_log (LOCAL->tmp,WARN);
  1756.       break;
  1757.     }
  1758.       }
  1759.  
  1760.       body->id = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1761.       body->description = imap_parse_string (stream,txtptr,reply,(long) NIL);
  1762.       if (s = imap_parse_string (stream,txtptr,reply,(long) NIL)) {
  1763.     ucase (s);        /* make parse easier */
  1764.     switch (*s++) {        /* dispatch based on encoding */
  1765.     case '7':        /* 7BIT */
  1766.       if (!strcmp (s,"BIT")) body->encoding = ENC7BIT;
  1767.       break;
  1768.     case '8':        /* 8BIT */
  1769.       if (!strcmp (s,"BIT")) body->encoding = ENC8BIT;
  1770.       break;
  1771.     case 'B':        /* BASE64 or BINARY */
  1772.       if (!strcmp (s,"ASE64")) body->encoding = ENCBASE64;
  1773.       else if (!strcmp (s,"INARY")) body->encoding = ENCBINARY;
  1774.       break;
  1775.     case 'Q':        /* QUOTED-PRINTABLE */
  1776.       if (!strcmp (s,"UOTED-PRINTABLE"))
  1777.         body->encoding = ENCQUOTEDPRINTABLE;
  1778.       break;
  1779.     default:
  1780.       break;
  1781.     }
  1782.       }
  1783.                 /* parse size of contents in bytes */
  1784.       body->size.bytes = imap_parse_number (stream,txtptr);
  1785.       switch (body->type) {    /* possible extra stuff */
  1786.       case TYPEMESSAGE:        /* message envelope and body */
  1787.     if (strcmp (body->subtype,"RFC822")) break;
  1788.     imap_parse_envelope (stream,&body->contents.msg.env,txtptr,reply);
  1789.     body->contents.msg.body = mail_newbody ();
  1790.     imap_parse_body_structure(stream,body->contents.msg.body,txtptr,reply);
  1791.                 /* drop into text case */
  1792.       case TYPETEXT:        /* size in lines */
  1793.     body->size.lines = imap_parse_number (stream,txtptr);
  1794.     break;
  1795.       default:            /* otherwise nothing special */
  1796.     break;
  1797.       }
  1798.       if (**txtptr != ')') {    /* validate ending */
  1799.     sprintf (LOCAL->tmp,"Junk at end of body part: %.80s",*txtptr);
  1800.     mm_log (LOCAL->tmp,WARN);
  1801.       }
  1802.       else ++*txtptr;        /* skip past delimiter */
  1803.     }
  1804.     break;
  1805.  
  1806.   case 'N':            /* if NIL */
  1807.   case 'n':
  1808.     ++*txtptr;            /* bump past "I" */
  1809.     ++*txtptr;            /* bump past "L" */
  1810.     break;
  1811.   default:            /* otherwise quite bogus */
  1812.     sprintf (LOCAL->tmp,"Bogus body structure: %.80s",*txtptr);
  1813.     mm_log (LOCAL->tmp,WARN);
  1814.     break;
  1815.   }
  1816. }
  1817.